﻿// <copyright file="Node.cs" company="City of Portland, BES-ASM">
// </copyright>
// <summary>Node class</summary>

namespace BESASM.EMGAATS.BusinessClasses
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.Threading.Tasks;

  /// <summary>
  /// Node class
  /// </summary>
  public class Node : Entity, INode
  {
    private string nodeName = string.Empty;
    private NodeType nodeType = NodeType.Manhole;
    private NodeTopType topType = NodeTopType.Ponding;
    private double diameter = 4.0;
    private double groundElevFt = 0.0;
    private IPoint geometry = null;

    /// <summary>
    /// Initializes a new instance of the <see cref="Node"/> class.
    /// </summary>
    /// <param name="id">The ID of the Node</param>
    public Node(int id)
      : base(EntityType.Node, id)
    {
    }

    /// <summary>
    /// Gets or sets the geometry of the Node
    /// </summary>
    public IPoint Geometry
    {
      get
      {
        return this.geometry;
      }

      set
      {
        if (value != this.geometry)
        {
          this.geometry = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets or sets the ground elevation for this node in feet.
    /// </summary>
    public double GroundElevFt 
    {
      get
      {
        return this.groundElevFt;
      }

      set
      {
        if (value != this.groundElevFt)
        {
          this.groundElevFt = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets or sets the user-friendly node name.
    /// </summary>
    public string NodeName
    {
      get
      {
        return this.nodeName;
      }

      set
      {
        if (value != this.nodeName)
        {
          this.nodeName = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets the x coordinate based on the point's geometry.
    /// </summary>
    public double X
    {
      get
      {
        if (this.Geometry != null)
        {
          return this.Geometry.X;
        }
        else
        {
          return double.NaN;
        }
      }
    }

    /// <summary>
    /// Gets the y coordinate based on the point's geometry.
    /// </summary>
    public double Y
    {
      get
      {
        if (this.Geometry != null)
        {
          return this.Geometry.Y;
        }
        else
        {
          return double.NaN;
        }
      }
    }

    /// <summary>
    /// Gets or sets type of node.
    /// </summary>
    public NodeType NodeType
    {
      get
      { 
        return this.nodeType;
      }

      set
      {
        if (!Enum.IsDefined(typeof(NodeType), value))
        {
          throw new ArgumentException("illegal node type");
        }
        else if (value != this.nodeType)
        {
          this.nodeType = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets the node invert elevation in feet
    /// </summary>
    public double NodeIEFt
    {
      get
      {
        double ie = 10000.0;

        var queryDS =
          from ds in this.Network.Links
          where ds.DSNode == this
          select ds.DSIEFt;

        var queryUS =
            from us in this.Network.Links
            where us.USNode == this
            select us.USIEFt;

        if (queryDS.Count() != 0)
        {
          ie = queryDS.Min();
        }

        if (queryUS.Count() != 0 && queryUS.Min() < ie)
        {
          ie = queryUS.Min();
        }

        return ie;
      }
    }

    /// <summary>
    /// Gets or sets an enumeration of the node top type controlling the fate of water when the node is filled to the top.
    /// </summary>
    public NodeTopType TopType
    {
      get
      {
        return this.topType;
      }

      set
      {
        if (!Enum.IsDefined(typeof(NodeTopType), value))
        {
          throw new ArgumentException("illegal node top type");
        }
        else if (value != this.topType)
        {
          this.topType = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets or sets the diameter of the vertical extent of the node in feet.
    /// </summary>
    public double DiameterFt 
    {
      get
      {
        return this.diameter;
      }

      set
      {
        if (value != this.diameter)
        {
          this.diameter = value;
          this.NotifyPropertyChanged();
        }
      }
    }

    /// <summary>
    /// Gets the collection of areas that drain directly to this node via a director.
    /// </summary>
    public List<IArea> UpstreamAreas
    {
      get
      {
        Network network = this.Network as Network;
        if (network != null)
        {
          return network.GetUpstreamAreas(this).ToList();
        }
        else
        {
          if (this.Network != null)
          {
            return this.Network.Areas.Where(area => area.Directors.Any(director => director.ToNode == this)).ToList();
          }
          else
          {
            return new List<IArea>();
          }
        }
      }
    }

    /// <summary>
    /// Gets the collection of upstream links that connect to this node.
    /// </summary>
    public List<ILink> UpstreamLinks
    {
      get
      {
        Network network = this.Network as Network;
        if (network != null)
        {
          return network.GetUpstreamLinks(this).ToList();
        }
        else
        {
          if (this.Network != null)
          {
            return this.Network.Links.Where(link => link.DSNode == this).ToList();
          }
          else
          {
            return new List<ILink>();
          }
        }
      }
    }

    /// <summary>
    /// Gets the collection of weirs that are attached to a node
    /// </summary>
    public List<IWeir> AttachedWeirs
    {
      get
      {
        Network network = this.Network as Network;
        if (network != null)
        {
          return network.GetAttachedWeirs(this).ToList();
        }
        else
        {
          if (this.Network != null)
          {
            return this.Network.Weirs.Where(weir => weir.USNode == this).ToList();
          }
          else
          {
            return new List<IWeir>();
          }
        }
      }
    }

    /// <summary>
    /// Gets a Storage object attached to a node, if available
    /// </summary>
    public IStorage AttachedStorage
    {
      get
      {
        Network network = this.Network as Network;
        if (network != null)
        {
          return network.GetAttachedStorage(this).First();
        }
        else
        {
          if (this.Network != null)
          {
            return this.Network.Storages.Where(storage => storage.Node == this).First();
          }
          else
          {
            return null;
          }
        }
      }
    }

    /// <summary>
    /// Gets the collection of orifices that are attached to a node
    /// </summary>
    public List<IOrifice> AttachedOrifices
    {
      get
      {
        Network network = this.Network as Network;
        if (network != null)
        {
          return network.GetAttachedOrifices(this).ToList();
        }
        else
        {
          if (this.Network != null)
          {
            return this.Network.Orifices.Where(orifice => orifice.USNode == this).ToList();
          }
          else
          {
            return new List<IOrifice>();
          }
        }
      }
    }

    /// <summary>
    /// Creates a new entity that is a copy of the current instance.
    /// </summary>
    /// <returns>A new object that is a shallow copy of this instance.</returns>
    public override object Clone()
    {
      Node clone = new Node(this.Id);

      // Simple properties and references
      // DO NOT copy Network, clones must be added to a network
      clone.GroundElevFt = this.GroundElevFt;
      clone.NodeName = this.NodeName;
      clone.NodeType = this.NodeType;
      clone.TopType = this.TopType;
      clone.DiameterFt = this.DiameterFt;

      // Geometry (copy)
      if (this.Geometry != null)
      {
        clone.Geometry = this.Geometry.Clone() as IPoint;
      }

      this.CloneAuditProperties(clone);

      clone.SetModified(false);
      return clone;
    }

    /// <summary>
    /// Returns a string representation of this node.
    /// </summary>
    /// <returns>A string representation of this node.</returns>
    public override string ToString()
    {
      return string.Format("{0} (Id: {1})", this.NodeName, this.Id);
    }
  }
}
